package com.mes.cloud.common.auth;

import com.mes.cloud.api.dto.request.employee.PositionListRequest;
import com.mes.cloud.api.dto.response.BaseEmployeeDto;
import com.mes.cloud.api.dto.response.employee.PositionListDto;
import com.mes.cloud.exception.BaseException;
import com.mes.cloud.common.JsonResult;
import com.mes.cloud.constant.Constants;
import com.mes.cloud.enums.HttpStatusEnum;
import com.mes.cloud.jedis.util.RedisUtil;
import com.mes.cloud.service.BaseEmployeeService;
import com.mes.cloud.service.SysEmployeePositionService;
import com.mes.cloud.service.SysRoleMenuService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@Component
public class AuthFilter implements Filter {

    final static Logger logger = LoggerFactory.getLogger(AuthFilter.class);

    private BaseEmployeeService baseEmployeeService;

    private RedisUtil redisUtil;

    private FilterIgnoreUrlConfig filterIgnoreUrlConfig;

    private SysEmployeePositionService sysEmployeePositionService;

    private SysRoleMenuService sysRoleMenuService;

    public AuthFilter( BaseEmployeeService baseEmployeeService, RedisUtil redisUtil, FilterIgnoreUrlConfig filterIgnoreUrlConfig, SysEmployeePositionService sysEmployeePositionService, SysRoleMenuService sysRoleMenuService) {
        this.baseEmployeeService = baseEmployeeService;
        this.redisUtil = redisUtil;
        this.filterIgnoreUrlConfig = filterIgnoreUrlConfig;
        this.sysEmployeePositionService = sysEmployeePositionService;
        this.sysRoleMenuService = sysRoleMenuService;
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        try {
            HttpServletRequest request = (HttpServletRequest) req;
            res.setCharacterEncoding("UTF-8");
            res.setContentType("application/json; charset=utf-8");
            resloveCross(request, res);

            //获取请求连接
            String spath = request.getServletPath();
            logger.info("请求连接spath:" + spath);
            //如果不需要拦截
            if (!needToFilter(spath)) {
                chain.doFilter(req, res);
                return;
            }

            //获取员工工号
            String token = request.getHeader("Authorization");
            //未登录 直接返401 跳登录
            if (StringUtils.isEmpty(token)) {
                throw new BaseException(HttpStatusEnum.UNAUTHORIZED.getCode(), HttpStatusEnum.UNAUTHORIZED.getMessage());
            }
            String employeeNo = redisUtil.get(token,String.class);
            logger.info(" current login user employeeNo is " + employeeNo);

            //判断用户是否登录 ，登录用户header 会存放用户工号 || 登录超时.超过多久没操作
            if (StringUtils.isEmpty(employeeNo)) {
                logger.error("employeeNo not exist,please login.");
                throw new BaseException(HttpStatusEnum.UNAUTHORIZED.getCode(), HttpStatusEnum.UNAUTHORIZED.getMessage());
            }
            redisUtil.expire(token, Constants.LOGIN_INFO_EXPIRE);
            //获取用户信息
            BaseEmployeeDto baseEmployeeDto = getBaseEmployeeDto(employeeNo);

            //获取用户权限,如果用户不存在给用户遴选角色权限
            Set<String> authUrlSet;
            if (baseEmployeeDto != null) {
                authUrlSet = getAuthurl(employeeNo);
            } else {
                //获取遴选角色的权限
                //获取遴选角色
                authUrlSet = getCullingauthUrl();
            }
            if (baseEmployeeDto != null) {
                //设置员工岗位信息
                BffContext.setPositionList(getEmpPosition(baseEmployeeDto));
            }
            //封装用户权限url
            BffContext.setPermissionSet(authUrlSet);

            //封装用户信息
            UserInfo userInfo = new UserInfo();
            if (baseEmployeeDto != null) {
                userInfo.setEmployeeNo(baseEmployeeDto.getEmployeeNo());
                userInfo.setRealName(baseEmployeeDto.getRealName());
                userInfo.setEmployeeId(baseEmployeeDto.getId());
                userInfo.setMainOrganizationName(baseEmployeeDto.getMainOrganizationName());
                userInfo.setMainPositionName(baseEmployeeDto.getMainPositionName());
                userInfo.setMainPositionId(baseEmployeeDto.getMainPositionId());
                userInfo.setMainOrganizationId(baseEmployeeDto.getMainOrganizationId());
                BffContext.setUserInfo(userInfo);
            } else {
                userInfo.setEmployeeNo(employeeNo);
                userInfo.setRealName(employeeNo);
                userInfo.setEmployeeId(employeeNo);
            }

            //封装requestFeignHeaders
//            HashMap<String, String> heads = new HashMap<>();
//            heads.put(UserInfoContext.USER_INFO, Base64Utils.encodeToString(JSON.toJSONString(userInfo, false).getBytes()));
//            requestFeignHeaders.setHeaders(heads);
            UserInfoContext.setUserInfo(userInfo);
            chain.doFilter(req, res);
        } catch (BaseException e) {
            //异常信息返回前端
            PrintWriter out = res.getWriter();
            JsonResult jsonResult = new JsonResult();
            out.append(jsonResult.setCode(e.getErrCode()).setMsg(e.getErrMsg()).toString());
            return;
        } finally {
            //一定要记得清理上下文
            BaseContextHandler.remove();
        }
    }

    /**
     * 解决跨域
     *
     * @param request
     * @param res
     */
    private void resloveCross(HttpServletRequest request, ServletResponse res) {
        HttpServletResponse response = (HttpServletResponse) res;
        //跨域访问CORS
        response.addHeader("Access-Control-Allow-Origin", "*");
        response.addHeader("Access-Control-Allow-Methods", "POST,OPTIONS,PUT,HEAD");
        response.addHeader("Access-Control-Max-Age", "3600000");
        response.addHeader("Access-Control-Allow-Credentials", "true");
        response.addHeader("Access-Control-Allow-Headers", "Authentication,Origin, X-Requested-With, Content-Type, Accept,token,Authorization");
        //让请求，不被缓存，
        response.setHeader("Cache-Control", "no-cache");
        response.setHeader("Cache-Control", "no-store");
        response.setHeader("Pragma", "no-cache");
        response.setDateHeader("Expires", 0);

        if (request.getMethod().equals("OPTIONS")) {
            response.setStatus(HttpServletResponse.SC_OK);
        }
    }

    @Override
    public void init(FilterConfig filterConfig) {
    }

    @Override
    public void destroy() {
    }

    /**
     * 获取员工信息
     * 查看redis是否缓存用户信息，缓存了直接获取，否则调用接口获取
     *
     * @param employeeNo
     * @return
     */
    private BaseEmployeeDto getBaseEmployeeDto(String employeeNo) {
        //判断redis是否存在 key=employeeNo+info
        String employeeNoInfoKey = employeeNo + Constants.USER_REDIS_SUFFIX;
        BaseEmployeeDto baseEmployeeDto = null;
        //查看redis是否缓存
        if (redisUtil.hasKey(employeeNoInfoKey)) {
            baseEmployeeDto = redisUtil.get(employeeNoInfoKey, BaseEmployeeDto.class);
            return baseEmployeeDto;
        }
        //调用接口获取用户信息
        //初始化feign header (必须初始化，否则报null)
        initFeignHeaders(employeeNo);
        JsonResult<BaseEmployeeDto> jsonResult = baseEmployeeService.getByEmployeeNo();
        //code=200 表示返回成功
        if (jsonResult.getCode() == HttpStatusEnum.OK.getCode()) {
            baseEmployeeDto = jsonResult.getData();
            //缓存用户信息
            redisUtil.set(employeeNoInfoKey, baseEmployeeDto);
            return baseEmployeeDto;
        }
        //记录不存在
        if (jsonResult.getCode() == HttpStatusEnum.RECORD_NOT_EXIST.getCode()) {
            return baseEmployeeDto;
        } else {
            throw new BaseException(jsonResult.getCode(), jsonResult.getMsg());
        }
    }

    /**
     * 初始化feign header
     *
     * @param employeeNo
     */
    private void initFeignHeaders(String employeeNo) {
        UserInfo userInfo = new UserInfo();
        userInfo.setEmployeeNo(employeeNo);
        UserInfoContext.setUserInfo(userInfo);
    }

    /**
     * 获取员工岗位
     * 查看redis是否缓存员工岗位，缓存了直接获取，否则调用接口获取
     *
     * @param baseEmployeeDto
     * @return Set<String>
     */
    private List<Position> getEmpPosition(BaseEmployeeDto baseEmployeeDto) {
        //判断redis是否存在 key=employeeId+position
        String employeePositionKey = baseEmployeeDto.getEmployeeNo() + Constants.POSITION_REDIS_SUFFIX;
        List<Position> positionList = null;
        //查看redis是否缓存
        if (redisUtil.hasKey(employeePositionKey)) {
            positionList = redisUtil.getObjectList(employeePositionKey, Position.class);
        } else {
            //调用接口获取用户信息
            //初始化feign header (必须初始化，否则报null)
            initFeignHeaders(baseEmployeeDto.getEmployeeNo());
            PositionListRequest positionListRequest = new PositionListRequest();
            positionListRequest.setEmployeeId(baseEmployeeDto.getId());
            JsonResult<List<PositionListDto>> jsonResultList = sysEmployeePositionService.positionList(positionListRequest);
            //code=200 表示返回成功
            if (jsonResultList.getCode() == HttpStatusEnum.OK.getCode()) {
                List<PositionListDto> positionListDtoList = jsonResultList.getData();
                positionList = copyDtoToPosition(positionListDtoList);
                //缓存用户权限
                redisUtil.set(employeePositionKey, positionList);
            } else {
                throw new BaseException(jsonResultList.getCode(), jsonResultList.getMsg());
            }
        }
        return positionList;
    }

    /**
     * 复制
     *
     * @param positionListDtoList
     * @return
     */
    private List<Position> copyDtoToPosition(List<PositionListDto> positionListDtoList) {
        List<Position> positionList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(positionListDtoList)) {
            for (PositionListDto positionListDto : positionListDtoList) {
                Position position = new Position();
                BeanUtils.copyProperties(positionListDto, position);
                positionList.add(position);
            }
        }
        return positionList;
    }

    /**
     * 获取员工拥有的authurl
     * 查看redis是否缓存authurl，缓存了直接获取，否则调用接口获取
     *
     * @param employeeNo
     * @return Set<String>
     */
    private Set<String> getAuthurl(String employeeNo) {
        //判断redis是否存在 key=employeeNo+menu
        String employeeNoAuthKey = employeeNo + Constants.AUTH_REDIS_SUFFIX;
        Set<String> authurlSet = null;
        //查看redis是否缓存
        if (redisUtil.hasKey(employeeNoAuthKey)) {
            authurlSet = redisUtil.get(employeeNoAuthKey, Set.class);
        } else {
            //调用接口获取用户信息
            //初始化feign header (必须初始化，否则报null)
            initFeignHeaders(employeeNo);
            JsonResult<Set<String>> jsonResult = baseEmployeeService.getAuthUrlByEmployeeNo();
            //code=200 表示返回成功
            if (jsonResult.getCode() == HttpStatusEnum.OK.getCode()) {
                authurlSet = jsonResult.getData();
                //缓存用户权限
                redisUtil.set(employeeNoAuthKey, authurlSet);
            } else {
                throw new BaseException(jsonResult.getCode(), jsonResult.getMsg());
            }
        }
        return authurlSet;
    }

    /**
     * 判断是否需要过滤
     *
     * @param spath 请求连接
     */
    public Boolean needToFilter(String spath) {
        logger.info("spath:" + spath);
        Boolean filterBoolean = true;
        //不需要过滤的资源
        List<String> urls = filterIgnoreUrlConfig.getUrls();
        for (String url : urls) {
            //请求连接是否在不需要过滤url范围内
            if (matches(url, spath)) {
                filterBoolean = false;
                //跳出循环
                break;
            }
        }
        return filterBoolean;
    }

    /**
     * 判断source是否符合pattern
     *
     * @param pattern
     * @param source
     * @return
     */
    private boolean matches(String pattern, String source) {
        if (pattern == null || source == null) {
            return false;
        }
        pattern = pattern.trim();
        source = source.trim();
        if (pattern.endsWith("*")) {
            // pattern: /druid* source:/druid/index.html
            int length = pattern.length() - 1;
            if (source.length() >= length) {
                if (pattern.substring(0, length).equals(
                        source.substring(0, length))) {
                    return true;
                }
            }
        } else if (pattern.startsWith("*")) {
            // pattern: *.html source:/xx/xx.html
            int length = pattern.length() - 1;
            if (source.length() >= length
                    && source.endsWith(pattern.substring(1))) {
                return true;
            }
        } else if (pattern.contains("*")) {
            // pattern:  /druid/*/index.html source:/druid/admin/index.html
            int start = pattern.indexOf("*");
            int end = pattern.lastIndexOf("*");
            if (source.startsWith(pattern.substring(0, start))
                    && source.endsWith(pattern.substring(end + 1))) {
                return true;
            }
        } else {
            // pattern: /druid/index.html source:/druid/index.html
            if (pattern.equals(source)) {
                return true;
            }
        }
        return false;
    }


    private HttpServletRequest getRequest() {
        ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        HttpServletRequest request = attr.getRequest();
        return request;
    }

    /**
     * 获取遴选角色权限
     *
     * @return
     */
    public Set<String> getCullingauthUrl() {
        //获取遴选
        JsonResult<Set<String>> jsonResult = sysRoleMenuService.getCullingAuthUrl();
        return jsonResult.getData();
    }
}
